﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using MobileSales.Common.Logger;
using MobileSales.Server.Database;
using MobileSales.Model.Exceptions;

namespace MobileSales.Server.Services
{
    public class ProductService : ServiceBase, IProductService
    {
        #region Product management

        #region GetProduct

        public MobileSales.Model.Products.Product GetProduct(int productId)
        {
            this.ValidateToken();
            var dbProduct = entities.Products.Where(x => x.ProductId.Equals(productId)).FirstOrDefault();
            if (dbProduct != null)
            {
                MobileSales.Model.Products.Product result = new MobileSales.Model.Products.Product
                {
                    Categories = this.GetProductCategories(productId).ToList(),
                    Description = dbProduct.Description,
                    Id = productId,
                    Images = this.GetProductImages(productId).ToList(),
                    IsMarketingOnlyItem = dbProduct.MarketingOnly,
                    MinQuantity = dbProduct.MinQuantity,
                    Name = dbProduct.Name,
                    OrderQuantity = dbProduct.OrderQuantity,
                    Price = dbProduct.Price,
                    Quantity = dbProduct.Quantity,
                    Sku = dbProduct.Sku,
                    Spread = dbProduct.Spread,
                    Symbol = dbProduct.Symb,
                    Tax = this.GetTax(dbProduct.TaxId),
                    TaxId = dbProduct.TaxId,
                    Unit = this.GetUnit(dbProduct.UnitId),
                    UnitId = dbProduct.UnitId,
                    Params = this.GetProductParams(productId)
                };
                return result;
            }
            else
            {
                CLogger.LogDebug("Could not find product");
            }
            return null;
        }

        #endregion

        #region StoreProduct
        public void StoreProduct(MobileSales.Model.Products.Product product)
        {
            this.ValidateToken();
            Product dbProduct = null;
            if (product.Id != 0)
            {
                dbProduct = entities.Products.Where(x => x.ProductId.Equals(product.Id)).FirstOrDefault();
                if (dbProduct == null)
                {
                    this.Raise(String.Format("Update failed! Cannot find product which id is {0}!", product.Id));
                }
            }
            if (dbProduct == null)
            {
                dbProduct = new Product();
            }

            dbProduct.Description = product.Description;
            dbProduct.MarketingOnly = product.IsMarketingOnlyItem;
            dbProduct.MinQuantity = product.MinQuantity;
            dbProduct.Name = product.Name;
            dbProduct.OrderQuantity = product.OrderQuantity;
            dbProduct.Price = product.Price;
            dbProduct.Quantity = product.Quantity;
            dbProduct.Sku = product.Sku;
            dbProduct.Spread = product.Spread;
            dbProduct.Symb = product.Symbol;
            dbProduct.TaxId = product.TaxId;
            dbProduct.UnitId = product.UnitId;

            if (product.Id == 0)
            {
                entities.Products.AddObject(dbProduct);
            }
            entities.SaveChanges();
            product.Id = dbProduct.ProductId;

            this.StoreProductCategories(product.Id, product.Categories);
            this.StoreProductParams(product.Id, product.Params);
            this.StoreProductImages(product.Id, product.Images);

            this.NotifyAll(Util.ServiceType.ProductService, "GetProduct", dbProduct.ProductId);
        }
        #endregion

        #region RemoveProduct
        public void RemoveProduct(int productId)
        {
            this.ValidateToken();
            Product dbProduct = entities.Products.Where(x => x.ProductId.Equals(productId)).FirstOrDefault();
            if (dbProduct != null)
            {
                entities.Products.DeleteObject(dbProduct);
                entities.SaveChanges();
                this.NotifyAll(Util.ServiceType.ProductService, "DeleteProduct", productId);
            }
            else
            {
                this.Raise("Remove failed! Could not find product in database.");
            }
        }
        #endregion

        #region GetProductList
        public IEnumerable<MobileSales.Model.Products.Product> GetProductList()
        {
            this.ValidateToken();
            return GetProductList(null);
        }

        public IEnumerable<MobileSales.Model.Products.Product> GetProductListByCategory(int categoryId)
        {
            this.ValidateToken();
            return GetProductList((int?)categoryId);
        }

        private IEnumerable<MobileSales.Model.Products.Product> GetProductList(int? categoryId)
        {
            IEnumerable<Product> dbProducts = null;
            if (categoryId.HasValue)
                dbProducts = entities.Products.Where(x => x.Categories.Select(y => y.CategoryId).Contains(categoryId.Value));
            else
                dbProducts = entities.Products;
            if (dbProducts != null)
            {
                List<MobileSales.Model.Products.Product> list = new List<MobileSales.Model.Products.Product>();
                foreach (var dbProduct in dbProducts)
                {
                    MobileSales.Model.Products.Product result = new MobileSales.Model.Products.Product
                    {
                        Categories = this.GetProductCategories(dbProduct.ProductId).ToList(),
                        Description = dbProduct.Description,
                        Id = dbProduct.ProductId,
                        Images = this.GetProductImages(dbProduct.ProductId).ToList(),
                        IsMarketingOnlyItem = dbProduct.MarketingOnly,
                        MinQuantity = dbProduct.MinQuantity,
                        Name = dbProduct.Name,
                        OrderQuantity = dbProduct.OrderQuantity,
                        Price = dbProduct.Price,
                        Quantity = dbProduct.Quantity,
                        Sku = dbProduct.Sku,
                        Spread = dbProduct.Spread,
                        Symbol = dbProduct.Symb,
                        Tax = this.GetTax(dbProduct.TaxId),
                        TaxId = dbProduct.TaxId,
                        Unit = this.GetUnit(dbProduct.UnitId),
                        UnitId = dbProduct.UnitId,
                        Params = this.GetProductParams(dbProduct.ProductId)
                    };
                    list.Add(result);
                }
                return list;
            }
            else
            {
                CLogger.LogDebug("Could not find products");
            }
            return null;
        }
        #endregion

        #region GetProductParams
        public Dictionary<MobileSales.Model.Params.Param, string> GetProductParams(int productId)
        {
            IEnumerable<ProductHasParam> dbProductParams = entities.ProductHasParams.Where(x => x.ProductId.Equals(productId));
            if (dbProductParams != null)
            {
                Dictionary<MobileSales.Model.Params.Param, string> paramDict = new Dictionary<MobileSales.Model.Params.Param, string>();
                foreach (var dbProductParam in dbProductParams)
                {

                    Param p = dbProductParam.Param;
                    MobileSales.Model.Params.Param paramKey = this.GetParam(dbProductParam.ParamId);
                    if (paramKey != null)
                    {
                        paramDict.Add(paramKey, dbProductParam.Value);
                    }
                    else
                        this.Raise("An error occured during getting some parameters");

                }
                return paramDict;
            }
            return null;
        }
        #endregion

        #region StoreProductParams
        public void StoreProductParams(int productId, Dictionary<MobileSales.Model.Params.Param, string> paramDict)
        {
            Product dbProduct = entities.Products.Where(x => x.ProductId.Equals(productId)).FirstOrDefault();
            if (dbProduct == null)
            {
                this.Raise("Update failed! Cannot store product params");
            }

            IEnumerable<ProductHasParam> dbProductParamList = dbProduct.ProductHasParams;
            foreach (var param in paramDict.Keys)
            {
                ProductHasParam dbProductParam = dbProductParamList.Where(x => x.ParamId.Equals(param.Id)).FirstOrDefault();
                bool exist = true;
                if (dbProductParam == null)
                {
                    exist = false;
                    dbProductParam = new ProductHasParam();
                }

                dbProductParam.ParamId = param.Id;
                dbProductParam.ProductId = productId;
                if (paramDict[param] == null) continue;
                dbProductParam.Value = paramDict[param].ToString();

                if (!exist)
                {
                    dbProduct.ProductHasParams.Add(dbProductParam);
                }
            }
            entities.SaveChanges();

            dbProductParamList = dbProduct.ProductHasParams;
            List<ProductHasParam> toRemove = new List<ProductHasParam>();
            foreach (var dbProductParam in dbProductParamList)
            {
                if (!paramDict.Keys.Select(x => x.Id).Contains(dbProductParam.ParamId))
                {
                    toRemove.Add(dbProductParam);
                }
            }
            foreach (var dbProductParam in toRemove)
            {
                dbProduct.ProductHasParams.Remove(dbProductParam);
            }
            entities.SaveChanges();
        }
        #endregion

        #region GetProductImages
        public IEnumerable<MobileSales.Model.Products.ProductImage> GetProductImages(int productId)
        {
            IEnumerable<ProductImage> dbProductImages = entities.ProductImages.Where(x => x.ProductId.Equals(productId)).OrderBy(x => x.Position);
            if (dbProductImages != null)
            {
                List<MobileSales.Model.Products.ProductImage> list = new List<MobileSales.Model.Products.ProductImage>();
                foreach (var dbProductImage in dbProductImages)
                {
                    list.Add(new MobileSales.Model.Products.ProductImage()
                    {
                        Id = dbProductImage.ProductImageId,
                        Path = dbProductImage.Path,
                        Position = dbProductImage.Position
                    });
                }
                return list;
            }
            return null;
        }
        #endregion

        #region StoreProductImages
        public void StoreProductImages(int productId, IList<MobileSales.Model.Products.ProductImage> images)
        {
            Product dbProduct = entities.Products.Where(x => x.ProductId.Equals(productId)).FirstOrDefault();
            if (dbProduct == null)
            {
                this.Raise("Update failed! Cannot store product images");
            }

            IEnumerable<ProductImage> dbImages = dbProduct.ProductImages;
            /* update or insert */
            foreach (var img in images)
            {
                ProductImage dbImage = null;
                if (img.Id != 0)
                {
                    dbImage = dbImages.Where(x => x.ProductImageId.Equals(img.Id)).FirstOrDefault();
                }
                if (dbImage == null)
                {
                    dbImage = new ProductImage();
                }

                dbImage.Path = img.Path;
                dbImage.Position = img.Position;
                dbImage.Product = dbProduct;
                if (img.Id == 0)
                {
                    dbProduct.ProductImages.Add(dbImage);
                }
            }
            /* remove */
            List<ProductImage> toRemove = new List<ProductImage>();
            foreach (var dbImg in dbImages)
            {
                if (!images.Select(x => x.Id).Contains(dbImg.ProductImageId))
                {
                    toRemove.Add(dbImg);
                }
            }
            foreach (var dbImg in toRemove)
            {
                dbProduct.ProductImages.Remove(dbImg);
            }
            entities.SaveChanges();
        }
        #endregion

        #region GetProductCategories
        public IEnumerable<MobileSales.Model.Products.Category> GetProductCategories(int productId)
        {
            Product dbProduct = entities.Products.Where(x => x.ProductId.Equals(productId)).FirstOrDefault();
            if (dbProduct == null) return null;

            IEnumerable<Category> dbCategories = dbProduct.Categories;
            if (dbCategories == null) return null;
            IList<MobileSales.Model.Products.Category> categories = new List<MobileSales.Model.Products.Category>();
            foreach (var dbCat in dbCategories)
            {
                categories.Add(new MobileSales.Model.Products.Category()
                {
                    Id = dbCat.CategoryId,
                    Description = dbCat.Description,
                    Name = dbCat.Name
                });
            }
            return categories;
        }
        #endregion

        #region StoreProductCategories
        public void StoreProductCategories(int productId, IList<MobileSales.Model.Products.Category> categories)
        {
            if (categories == null) return;
            if (categories.Count() == 0) return;


            Product dbProduct = entities.Products.Where(x => x.ProductId.Equals(productId)).FirstOrDefault();
            if (dbProduct == null)
            {
                this.Raise("Update failed! Cannot store product categories");
            }
            var productCatIds = dbProduct.Categories.Select(y => y.CategoryId);
            var catIds = categories.Select(z => z.Id);
            IEnumerable<Category> addList = entities.Categories.Where(x => (!productCatIds.Contains(x.CategoryId)
                                                                        && catIds.Contains(x.CategoryId)));
            IEnumerable<Category> removeList = dbProduct.Categories.Where(x => !catIds.Contains(x.CategoryId));
            foreach (Category cat in addList)
            {
                dbProduct.Categories.Add(cat);
            }
            List<Category> toRemove = new List<Category>();
            foreach (Category cat in removeList)
            {
                toRemove.Add(cat);
            }
            foreach (Category cat in toRemove)
            {
                dbProduct.Categories.Remove(cat);
            }
            entities.SaveChanges();
        }
        #endregion

        #endregion

        #region Category management

        #region GetCategory
        public MobileSales.Model.Products.Category GetCategory(int categoryId)
        {
            this.ValidateToken();
            Category dbCategory = entities.Categories.Where(x => x.CategoryId.Equals(categoryId)).FirstOrDefault();
            MobileSales.Model.Products.Category category = null;
            if (dbCategory != null)
            {
                category = new MobileSales.Model.Products.Category()
                {
                    Id = dbCategory.CategoryId,
                    Description = dbCategory.Description,
                    Name = dbCategory.Name,
                    ParentId = dbCategory.ParentCategoryId
                };
            }
            return category;
        }
        #endregion

        #region StoreCategory
        public void StoreCategory(MobileSales.Model.Products.Category category)
        {
            this.ValidateToken();
            Category dbCategory = null;
            if (category.Id != 0)
            {
                dbCategory = entities.Categories.Where(x => x.CategoryId.Equals(category.Id)).FirstOrDefault();
            }
            if (dbCategory == null)
            {
                dbCategory = new Category();
            }
            dbCategory.Name = category.Name;
            dbCategory.Description = category.Description;
            if (category.ParentId.HasValue)
            {
                if (dbCategory.CategoryId != 0)
                {
                    /* validation */
                    Category currentCat = entities.Categories.Where(x => x.CategoryId.Equals(category.ParentId.Value)).FirstOrDefault();
                    while (currentCat != null)
                    {
                        if (currentCat.CategoryId.Equals(dbCategory.CategoryId))
                            this.Raise("Desination folder is a source subfolder!");
                        currentCat = currentCat.ParentCategory;
                    }
                }
            }
            dbCategory.ParentCategoryId = category.ParentId;

            if (category.Id == 0)
            {
                entities.Categories.AddObject(dbCategory);
            }
            entities.SaveChanges();
            this.NotifyAll(Util.ServiceType.ProductService, "GetCategory", dbCategory.CategoryId);
        }
        #endregion

        #region RemoveCategory
        public void RemoveCategory(int categoryId)
        {
            this.ValidateToken();
            Category dbCategory = entities.Categories.Where(x => x.CategoryId.Equals(categoryId)).FirstOrDefault();
            if (dbCategory != null)
            {
                entities.Categories.DeleteObject(dbCategory);
                entities.SaveChanges();
                this.NotifyAll(Util.ServiceType.ProductService, "DeleteCategory", categoryId);
            }
            else
            {
                this.Raise("Cannot remove category! Could not find category in database!");
            }
        }
        #endregion

        #region GetCategoryTree

        public IEnumerable<MobileSales.Model.Products.Category> GetCategoryTree()
        {
            this.ValidateToken();
            IEnumerable<Category> dbCategories = entities.Categories.Where(x => x.ParentCategoryId == null);
            if (dbCategories != null)
            {
                IList<MobileSales.Model.Products.Category> categories = new List<MobileSales.Model.Products.Category>();
                foreach (var dbCat in dbCategories)
                {
                    categories.Add(GetCategoryTree(dbCat));
                }
                return categories;
            }
            return null;
        }

        public MobileSales.Model.Products.Category GetCategoryTreeByBranch(int branchCategoryId)
        {
            Category dbCategory = entities.Categories.Where(x => x.CategoryId.Equals(branchCategoryId)).FirstOrDefault();
            if (dbCategory != null)
            {
                return GetCategoryTree(dbCategory);
            }
            return null;
        }

        private MobileSales.Model.Products.Category GetCategoryTree(Category root)
        {
            MobileSales.Model.Products.Category cat = new MobileSales.Model.Products.Category()
            {
                Id = root.CategoryId,
                Name = root.Name,
                Description = root.Description
            };
            if (root.ChildrenCategories != null && root.ChildrenCategories.Count() > 0)
            {
                cat.Children = new List<MobileSales.Model.Products.Category>();
                foreach (var dbChild in root.ChildrenCategories)
                {
                    var c = GetCategoryTree(dbChild);
                    //c.Parent = cat;
                    c.ParentId = cat.Id;
                    cat.Children.Add(c);
                }
            }
            return cat;
        }

        #endregion

        #region GetCategoryList
        public IEnumerable<MobileSales.Model.Products.Category> GetCategoryList(IEnumerable<int> categoryIds)
        {
            this.ValidateToken();
            IEnumerable<Category> dbCategories = null;
            if (categoryIds != null)
                dbCategories = entities.Categories.Where(x => categoryIds.Contains(x.CategoryId));
            else
                dbCategories = entities.Categories;

            IList<MobileSales.Model.Products.Category> list = new List<MobileSales.Model.Products.Category>();
            foreach (var dbCategory in dbCategories)
            {
                MobileSales.Model.Products.Category category = new MobileSales.Model.Products.Category()
                {
                    Id = dbCategory.CategoryId,
                    Description = dbCategory.Description,
                    Name = dbCategory.Name,
                    ParentId = dbCategory.ParentCategoryId
                };
                list.Add(category);
            }
            return list;
        }
        #endregion

        #region GetParentCategory
        public MobileSales.Model.Products.Category GetParentCategory(int categoryId)
        {
            this.ValidateToken();
            Category dbCategory = entities.Categories.Where(x => x.CategoryId.Equals(categoryId)).FirstOrDefault();
            if (dbCategory != null && dbCategory.ParentCategoryId.HasValue)
            {
                return this.GetCategory(dbCategory.ParentCategoryId.Value);
            }
            return null;
        }
        #endregion

        #region GetChildrenCategories
        public IEnumerable<MobileSales.Model.Products.Category> GetChildrenCategories(int categoryId)
        {
            this.ValidateToken();
            Category dbCategory = entities.Categories.Where(x => x.CategoryId.Equals(categoryId)).FirstOrDefault();
            if (dbCategory != null && dbCategory.ChildrenCategories != null)
            {
                List<MobileSales.Model.Products.Category> list = new List<MobileSales.Model.Products.Category>();
                foreach (var dbChild in dbCategory.ChildrenCategories)
                {
                    list.Add(new MobileSales.Model.Products.Category()
                    {
                        Id = dbChild.CategoryId,
                        Description = dbChild.Description,
                        Name = dbChild.Name
                    });
                }
                return list;

            }
            return null;
        }
        #endregion

        #endregion

        #region Tax management

        #region GetTaxList
        public IEnumerable<MobileSales.Model.Products.Tax> GetTaxList()
        {
            this.ValidateToken();
            return entities.Taxes.Select(x => new MobileSales.Model.Products.Tax() { Id = x.TaxId, Value = x.Value });
        }
        #endregion

        #region GetTax
        public MobileSales.Model.Products.Tax GetTax(int taxId)
        {
            this.ValidateToken();
            return entities.Taxes.Where(x => x.TaxId.Equals(taxId))
                                  .Select(x => new MobileSales.Model.Products.Tax() { Id = x.TaxId, Value = x.Value })
                                  .FirstOrDefault();
        }
        #endregion

        #region StoreTax
        public void StoreTax(MobileSales.Model.Products.Tax tax)
        {
            this.ValidateToken();
            Tax dbTax = null;
            if (tax.Id != 0)
            {
                dbTax = entities.Taxes.Where(x => x.TaxId.Equals(tax.Id)).FirstOrDefault();
                if (dbTax == null)
                    this.Raise("Could not find tax rate in database!");
            }
            if (dbTax == null)
            {
                dbTax = new Tax();
            }
            dbTax.Value = tax.Value;
            if (tax.Id == 0)
            {
                entities.Taxes.AddObject(dbTax);
            }
            entities.SaveChanges();
            this.NotifyAll(Util.ServiceType.ProductService, "GetTax", dbTax.TaxId);
        }
        #endregion

        #region RemoveTax
        public void RemoveTax(int taxId)
        {
            this.ValidateToken();
            Tax dbTax = entities.Taxes.Where(x => x.TaxId.Equals(taxId)).FirstOrDefault();
            if (dbTax != null)
            {
                entities.Taxes.DeleteObject(dbTax);
                entities.SaveChanges();
                this.NotifyAll(Util.ServiceType.ProductService, "DeleteTax", taxId);
            }
            else
            {
                this.Raise("Could not find tax rate in database!");
            }
        }
        #endregion

        #endregion

        #region Unit management

        #region GetUnitList
        public IEnumerable<MobileSales.Model.Products.Unit> GetUnitList()
        {
            this.ValidateToken();
            IEnumerable<MobileSales.Model.Products.Unit> result = entities.Units.Select(x => new MobileSales.Model.Products.Unit() { Id = x.UnitId, Name = x.Name });
            return result;
        }
        #endregion

        #region GetUnit
        public MobileSales.Model.Products.Unit GetUnit(int unitId)
        {
            this.ValidateToken();
            return entities.Units.Where(x => x.UnitId.Equals(unitId))
                                  .Select(x => new MobileSales.Model.Products.Unit() { Id = x.UnitId, Name = x.Name })
                                  .FirstOrDefault();
        }
        #endregion

        #region StoreUnit
        public void StoreUnit(MobileSales.Model.Products.Unit unit)
        {
            this.ValidateToken();
            Unit dbUnit = null;
            if (unit.Id != 0)
            {
                dbUnit = entities.Units.Where(x => x.UnitId.Equals(unit.Id)).FirstOrDefault();
                if (dbUnit == null)
                {
                    this.Raise("Could not find unit in database!");
                }

            }
            if (dbUnit == null)
            {
                dbUnit = new Unit();
            }
            dbUnit.Name = unit.Name;
            if (unit.Id == 0)
            {
                entities.Units.AddObject(dbUnit);
            }
            entities.SaveChanges();
            this.NotifyAll(Util.ServiceType.ProductService, "GetUnit", dbUnit.UnitId);
        }
        #endregion

        #region RemoveUnit
        public void RemoveUnit(int unitId)
        {
            this.ValidateToken();
            Unit dbUnit = entities.Units.Where(x => x.UnitId.Equals(unitId)).FirstOrDefault();
            if (dbUnit != null)
            {
                entities.Units.DeleteObject(dbUnit);
                entities.SaveChanges();
                this.NotifyAll(Util.ServiceType.ProductService, "DeleteUnit", unitId);
            }
            else
            {
                this.Raise("Could not find unit in database!");
            }
        }
        #endregion

        #endregion

        #region Product image management
        #region StoreProductImage
        public void StoreProductImage(int productId, MobileSales.Model.Products.ProductImage productImage)
        {
            Product dbProduct = entities.Products.Where(x => x.ProductId.Equals(productId)).FirstOrDefault();
            if (dbProduct != null)
            {
                ProductImage dbProductImg = null;
                if (productImage.Id != 0)
                {
                    dbProductImg = dbProduct.ProductImages.Where(x => x.ProductImageId.Equals(productImage.Id)).FirstOrDefault();
                    if (dbProductImg == null)
                        this.Raise("could not get product image");
                }
                else
                {
                    dbProductImg = new ProductImage();
                }
                dbProductImg.Position = productImage.Position;
                dbProductImg.Path = productImage.Path;

                if (productImage.Id == 0)
                {
                    dbProduct.ProductImages.Add(dbProductImg);
                }
                entities.SaveChanges();
                this.NotifyAll(Util.ServiceType.ProductService, "GetProduct", productId);
            }
            else
            {
                this.Raise("could not get product");
            }
        }
        #endregion

        #region DeleteProductImage
        public void DeleteProductImage(int productImageId)
        {
            ProductImage dbProductImg = entities.ProductImages.Where(x => x.ProductImageId.Equals(productImageId)).FirstOrDefault();

            if (dbProductImg != null)
            {
                int productId = dbProductImg.ProductId;
                entities.ProductImages.DeleteObject(dbProductImg);
                entities.SaveChanges();
                this.NotifyAll(Util.ServiceType.ProductService, "GetProduct", productId);
            }
            else
            {
                this.Raise("could not get product image");
            }

        }
        #endregion 
        #endregion

    }
}
